﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace RFC.AOP
{
    /// <summary>
    /// Apect代理类生成
    /// </summary>
    public class ApectProxyBuilder
    {

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TInterface">泛型接口</typeparam>
        /// <typeparam name="TImplement">泛型实现类</typeparam>
        /// <param name="parameterObjects"></param>
        /// <returns></returns>
        public static TInterface CreateInstance<TInterface, TImplement>(params object[] parameterObjects)
            where TInterface : class
            where TImplement : class
        {
            var classType = BuildType<TInterface, TImplement>();
            return (TInterface)Activator.CreateInstance(classType, parameterObjects);
        }

        public static Type BuildType<TInterface, TImplement>()
            where TInterface : class
            where TImplement : class
        {
            Type interfaceType = typeof(TInterface);
            Type implementType = typeof(TImplement);
            var findInterfaces = implementType.GetInterfaces();
            if (findInterfaces.All(a => a != interfaceType))
            {
                throw new InvalidCastException($"实现类：{implementType.Name}未继承接口：{interfaceType.Namespace}");
            }
            return BuildType(interfaceType, implementType);
        }

        /// <summary>
        /// 创建一个实例对象
        /// </summary>
        /// <typeparam name="TImplement"></typeparam>
        /// <param name="parameterObjects"></param>
        /// <returns></returns>
        public static TImplement CreateInstance<TImplement>(params object[] parameterObjects) where TImplement : class
        {
            var classType = BuildType<TImplement>();
            return (TImplement)Activator.CreateInstance(classType, parameterObjects);
        }

        /// <summary>
        /// 生成一个对象类型
        /// </summary>
        /// <typeparam name="TImplement"></typeparam>
        /// <returns></returns>
        public static Type BuildType<TImplement>() where TImplement : class
        {
            Type implementType = typeof(TImplement);
            return BuildType(null, implementType);
        }

        private static Type BuildType(Type interfaceType, Type implementType)
        {
            var moduleName = implementType.Name;
            string dllName = $"{implementType.Name}_Aop.dll";
            string className = $"{moduleName}_Proxy";
            AssemblyName assemblyName = new AssemblyName(moduleName);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(className, dllName);
            Type paretnType;
            Type[] interfaceTypes;
            if (interfaceType == null)
            {
                interfaceTypes = Type.EmptyTypes;
                paretnType = implementType;
            }
            else
            {
                interfaceTypes = new[] { interfaceType };
                paretnType = null;
            }
            TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, paretnType, interfaceTypes);

            //定义一个全局私有变量
            var fieldType = interfaceType;
            if (fieldType == null) fieldType = implementType;
            FieldBuilder implementBaseFieldBuilder = typeBuilder.DefineField($"_{implementType.Name}", fieldType, FieldAttributes.Private | FieldAttributes.InitOnly);

            //实现构造函数
            BuildCtor(ref typeBuilder, implementBaseFieldBuilder, implementType);

            //开始创建方法
            MethodInfo[] needImplMethods;
            if (interfaceType != null)
            {
                needImplMethods = interfaceType.GetMethods();
            }
            else
            {
                needImplMethods = implementType.GetMethods();
            }
            foreach (var interfaceMethodInfo in needImplMethods)
            {
                if (interfaceMethodInfo.Name.Equals("ToString")) continue;
                if (interfaceMethodInfo.Name.Equals("GetType")) continue;
                if (interfaceMethodInfo.Name.Equals("GetHashCode")) continue;
                if (interfaceMethodInfo.Name.Equals("Equals")) continue;
                if (interfaceMethodInfo.IsSpecialName) continue;
                BuildMethod(ref typeBuilder, implementBaseFieldBuilder, interfaceMethodInfo);
            }
            var proxyType = typeBuilder.CreateType();
            assemblyBuilder.Save(dllName);
            return proxyType;
        }

        static void BuildCtor(ref TypeBuilder typeBuilder, FieldBuilder implementBaseTypeFieldBuilder, Type implementBaseType)
        {
            foreach (var ctor in implementBaseType.GetConstructors(BindingFlags.Public | BindingFlags.Instance))
            {
                var parameterTypes = ctor.GetParameters().Select(u => u.ParameterType).ToArray();
                var ilGeneratorForCtor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, parameterTypes);
                ILGenerator ilGenerator = ilGeneratorForCtor.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);//加载this进来
                ConstructorInfo objectConstructorInfo = typeof(object).GetConstructor(Type.EmptyTypes);
                ilGenerator.Emit(OpCodes.Call, objectConstructorInfo);//会调用object.ctor，其实默认就是调用自己ctor
                //再次加载构造函数参数到计算堆栈
                ilGenerator.Emit(OpCodes.Ldarg_0);//加载this进来
                for (int i = 0; i < parameterTypes.Length; i++)
                {
                    ilGenerator.Emit(OpCodes.Ldarg, i + 1);
                }
                ilGenerator.Emit(OpCodes.Newobj, ctor);
                ilGenerator.Emit(OpCodes.Stfld, implementBaseTypeFieldBuilder);
                ilGenerator.Emit(OpCodes.Ret);
            }
        }

        static void BuildMethod(ref TypeBuilder typeBuilder, FieldBuilder implementBaseTypeFieldBuilder, MethodInfo executeMethodInfo)
        {
            var parameters = executeMethodInfo.GetParameters();
            var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
            if (parameters.Length == 0) parameterTypes = Type.EmptyTypes;
            var methodAttributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual;
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(executeMethodInfo.Name, methodAttributes, executeMethodInfo.ReturnType, parameterTypes);
            ILGenerator ilGenerator = methodBuilder.GetILGenerator();
            var apectContextBuilder = BuildApectContext(ref ilGenerator, executeMethodInfo, parameterTypes, implementBaseTypeFieldBuilder);

            //找寻ApectAttribute
            var apectAttributes = executeMethodInfo.GetCustomAttributes<ApectBaseAttribute>().ToList();
            LocalBuilder[] attributeLocalBuilders = new LocalBuilder[apectAttributes.Count];
            Type[] apectAttributeTypes = new Type[apectAttributes.Count];
            for (int i = 0; i < apectAttributes.Count; i++)
            {
                var apectBaseAttribute = apectAttributes[i];
                var attributeType = apectBaseAttribute.GetType();
                apectAttributeTypes[i] = attributeType;
                var attributeLocalBuilder = BuildApectAttribute(ref ilGenerator, attributeType);
                attributeLocalBuilders[i] = attributeLocalBuilder;
                BeforeCall(ref ilGenerator, apectContextBuilder, attributeLocalBuilder, attributeType);
            }

            //实现方法内部
            bool isReturnVoid = executeMethodInfo.ReturnType == typeof(void);
            LocalBuilder resultLocal = null;
            if (!isReturnVoid)
                resultLocal = ilGenerator.DeclareLocal(executeMethodInfo.ReturnType);
            ilGenerator.Emit(OpCodes.Ldarg_0);//加载this进来
            ilGenerator.Emit(OpCodes.Ldfld, implementBaseTypeFieldBuilder);
            if (parameterTypes.Length > 0)
            {
                for (int i = 0; i < parameterTypes.Length; i++)
                    ilGenerator.Emit(OpCodes.Ldarg, i + 1);
            }
            ilGenerator.Emit(OpCodes.Callvirt, executeMethodInfo);
            if (!isReturnVoid)
            {
                ilGenerator.Emit(OpCodes.Stloc, resultLocal);
                ilGenerator.Emit(OpCodes.Ldloc, apectContextBuilder);
                ilGenerator.Emit(OpCodes.Ldloc, resultLocal);
                if (executeMethodInfo.ReturnType.IsValueType)
                    ilGenerator.Emit(OpCodes.Box, executeMethodInfo.ReturnType);
                ilGenerator.Emit(OpCodes.Call, typeof(ApectContext).GetMethod("SetReturnObject", new[] { typeof(object) }));
            }

            //调用AfterCall
            for (int i = 0; i < attributeLocalBuilders.Length; i++)
            {
                var attributeLocalBuilder = attributeLocalBuilders[i];
                var attributeType = apectAttributeTypes[i];
                AfterCall(ref ilGenerator, apectContextBuilder, attributeLocalBuilder, attributeType);
            }

            //如果有返回值就返回
            if (!isReturnVoid)
                ilGenerator.Emit(OpCodes.Ldloc, resultLocal);
            ilGenerator.Emit(OpCodes.Ret);
        }

        static LocalBuilder BuildApectContext(ref ILGenerator ilGenerator, MethodInfo interfaceMethodInfo, Type[] parameterTypes, FieldBuilder implementBaseTypeFieldBuilder)
        {
            //定义一个ApectContext
            var apectContextType = typeof(ApectContext);
            var apectContextBuilder = ilGenerator.DeclareLocal(apectContextType);
            ConstructorInfo constructorInfo = apectContextType.GetConstructor(Type.EmptyTypes);
            if (constructorInfo == null) throw new ArgumentNullException(nameof(constructorInfo));
            ilGenerator.Emit(OpCodes.Newobj, constructorInfo);
            ilGenerator.Emit(OpCodes.Stloc, apectContextBuilder); //放入到loc_0调用栈里

            //记录本地方法
            ilGenerator.Emit(OpCodes.Ldloc, apectContextBuilder);
            ilGenerator.Emit(OpCodes.Ldstr, interfaceMethodInfo.Name);
            ilGenerator.Emit(OpCodes.Call, apectContextType.GetMethod("SetMethodName", new[] { typeof(string) }));

            //记录本地参数
            var parameterObjectBuilder = ilGenerator.DeclareLocal(typeof(object[]));
            ilGenerator.Emit(OpCodes.Ldc_I4, parameterTypes.Length);
            ilGenerator.Emit(OpCodes.Newarr, typeof(object));
            ilGenerator.Emit(OpCodes.Stloc, parameterObjectBuilder);
            for (int i = 0; i < parameterTypes.Length; i++)
            {
                ilGenerator.Emit(OpCodes.Ldloc, parameterObjectBuilder);
                ilGenerator.Emit(OpCodes.Ldc_I4, i);
                ilGenerator.Emit(OpCodes.Ldarg, i + 1);
                if (parameterTypes[i].IsValueType)
                    ilGenerator.Emit(OpCodes.Box, parameterTypes[i]);
                ilGenerator.Emit(OpCodes.Stelem_Ref);
            }
            ilGenerator.Emit(OpCodes.Ldloc, apectContextBuilder);
            ilGenerator.Emit(OpCodes.Ldloc, parameterObjectBuilder);
            ilGenerator.Emit(OpCodes.Call, apectContextType.GetMethod("SetParameters", new[] { typeof(object[]) }));

            //记录原生类执行者
            ilGenerator.Emit(OpCodes.Ldloc, apectContextBuilder);
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldfld, implementBaseTypeFieldBuilder);
            ilGenerator.Emit(OpCodes.Call, apectContextType.GetMethod("SetExecutor", new[] { typeof(object) }));

            //返回ApectContext
            return apectContextBuilder;
        }

        static LocalBuilder BuildApectAttribute(ref ILGenerator ilGenerator, Type attributeType)
        {
            var attributeLocalBuilder = ilGenerator.DeclareLocal(attributeType);
            ConstructorInfo attributeConstructorInfo = attributeType.GetConstructor(Type.EmptyTypes);
            ilGenerator.Emit(OpCodes.Newobj, attributeConstructorInfo);
            ilGenerator.Emit(OpCodes.Stloc, attributeLocalBuilder);
            return attributeLocalBuilder;
        }

        static void BeforeCall(ref ILGenerator ilGenerator, LocalBuilder apectContextBuilder, LocalBuilder attributeLocalBuilder, Type attributeType)
        {
            ilGenerator.Emit(OpCodes.Ldloc, attributeLocalBuilder);
            ilGenerator.Emit(OpCodes.Ldloc, apectContextBuilder);
            ilGenerator.Emit(OpCodes.Callvirt, attributeType.GetMethod("Before"));
        }

        static void AfterCall(ref ILGenerator ilGenerator, LocalBuilder apectContextBuilder, LocalBuilder attributeLocalBuilder, Type attributeType)
        {
            ilGenerator.Emit(OpCodes.Ldloc, attributeLocalBuilder);
            ilGenerator.Emit(OpCodes.Ldloc, apectContextBuilder);
            ilGenerator.Emit(OpCodes.Callvirt, attributeType.GetMethod("After"));
        }
    }
}
